package cn.com.bluemoon.daps.common.datascope.feign;

import cn.com.bluemoon.daps.common.constant.DapConstant;
import cn.com.bluemoon.daps.common.toolkit.BmAssetUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * <pre>
 * @EnableFeignClients(basePackages = FeignClientAutoConfig.FEIGN_BASE_PKG, defaultConfiguration = FeignRequestInterceptor.class)
 * @EnableHystrix
 * public class FeignClientAutoConfig {
 *     public static final String FEIGN_BASE_PKG = "cn.com.bluemoon.xxx";
 *     @Bean
 *     public FeignRequestInterceptor feignRequestInterceptor() {
 *         return new FeignRequestInterceptor();
 *     }
 *     @Bean
 *     public FeignHystrixConcurrencyStrategy feignHystrixConcurrencyStrategy() {
 *         return new FeignHystrixConcurrencyStrategy();
 *     }
 * }
 * </pre>
 * 微服务之间使用Feign调用接口时, 透传header参数信息
 * 1.默认为hystrix.command.default.execution.isolation.strategy=Thread 会获取不到ServletRequestAttributes
 * 2.调整为hystrix.command.default.execution.isolation.strategy=SEMAPHORE 可以获取，但是非官方推荐
 * fix bug [SpringCloud Feign调用报错feign.RetryableException: too many bytes written executing]
 *
 * @author Jarod.Kong
 */
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
    /**
     * 内网代理ip请求头key
     */
    public static final String X_INNER_PROXY_CLIENT_IP = "X-Inner-Proxy-Client-IP";

    @Value("${daps.mock.enabled:false}")
    private Boolean mockEnabled;

    @Value("${daps.mock.token:''}")
    private String mockToken;

    public FeignRequestInterceptor() {
    }

    /**
     * 用于模拟用户token
     *
     * @return token optional
     */
    private Optional<String> initMockToken() {
        if (Boolean.TRUE.equals(mockEnabled)) {
            String token = mockToken;
            BmAssetUtils.notNull(token, "本地测试启动获取模拟token失败！");
            return Optional.of(token);
        }
        return Optional.empty();
    }

    @Override
    public void apply(RequestTemplate requestTemplate) {
        initMockToken().ifPresent(mockToken -> {
            requestTemplate.header(defineTokenName(), mockToken);
            requestTemplate.query(defineTokenName(), mockToken);
            log.warn("WARN!!! 【单元测试】中启动了模拟用户token=[{}]进行请求服务", mockToken);
        });
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // WARN !! 针对业务逻辑中使用的是异步线程进行调用FeignClient服务，导致token还是没有传递到异步的线程上
        if (requestAttributes instanceof ServletRequestAttributes) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            BmAssetUtils.notNull(attributes, "获取当前web环境request servlet失败");
            HttpServletRequest request = attributes.getRequest();
            Enumeration<String> headerNames = request.getHeaderNames();
            if (headerNames != null) {
                while (headerNames.hasMoreElements()) {
                    String name = headerNames.nextElement();
                    String values = request.getHeader(name);
                    // 跳过 content-length fix bug["SpringCloud Feign调用报错feign.RetryableException: too many bytes written executing"]
                    if ("content-length".equals(name)) {
                        continue;
                    }
                    requestTemplate.header(name, values);
                }
            }
            // 请求头信息
            Map<String, Collection<String>> headers = requestTemplate.headers();
            Collection<String> tokens = headers.get(defineTokenName());
            if (tokens == null || tokens.isEmpty()) {
                // fix 兼容token来源可能是param和请求头的情况
                Map<String, String[]> parameterMap = request.getParameterMap();
                String[] token = parameterMap.get(defineTokenName());
                if (token != null && token.length > 0) {
                    requestTemplate.query(defineTokenName(), token);
                    requestTemplate.header(defineTokenName(), token);
                    log.info("feign请求：获取到来自请求参数token信息:{}", Arrays.toString(token));
                }
            } else {
                log.debug("feign请求：获取到来自请求头token信息：{}", tokens);
            }

        }
    }

    /**
     * 目前只允许重写该方法进行调整token键
     *
     * @return token键
     */
    protected String defineTokenName() {
        return DapConstant.WEB_HTTP_TOKEN_NAME;
    }
}